package org.linlinjava.litemall.admin.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.github.binarywang.wxpay.service.WxPayService;
import lombok.RequiredArgsConstructor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.linlinjava.litemall.admin.constants.Constants;
import org.linlinjava.litemall.admin.dto.ShipDTO;
import org.linlinjava.litemall.admin.util.CloneUtil;
import org.linlinjava.litemall.admin.util.PayUtil;
import org.linlinjava.litemall.admin.vo.OrderInfoVo;
import org.linlinjava.litemall.admin.vo.OrderInvoiceVo;
import org.linlinjava.litemall.core.express.ExpressService;
import org.linlinjava.litemall.core.express.dao.ExpressInfoVO;
import org.linlinjava.litemall.core.express.dao.TracesVO;
import org.linlinjava.litemall.core.notify.NotifyService;
import org.linlinjava.litemall.core.notify.NotifyType;
import org.linlinjava.litemall.core.util.JacksonUtil;
import org.linlinjava.litemall.core.util.ResponseUtil;
import org.linlinjava.litemall.db.dao.LitemallInvoiceMapper;
import org.linlinjava.litemall.db.domain.*;
import org.linlinjava.litemall.db.service.*;
import org.linlinjava.litemall.db.util.DateUtil;
import org.linlinjava.litemall.db.util.OrderUtil;
import org.linlinjava.litemall.db.util.UuidUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;

import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.security.KeyStore;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static org.linlinjava.litemall.admin.util.AdminResponseCode.*;

@Service
@RequiredArgsConstructor(onConstructor = @_(@Autowired))
public class AdminOrderService {
    private final Log logger = LogFactory.getLog(AdminOrderService.class);

    private final OrderGoodsService orderGoodsService;
    private final OrderService orderService;
    private final GoodsService goodsService;
    private final UserService userService;
    private final CommentService commentService;
    private final NotifyService notifyService;
    private final LogHelper logHelper;

    private final ExpressService expressService;
    private final LitemallInvoiceMapper litemallInvoiceMapper;

    // 连接超时时间，默认10秒
    private  int socketTimeout = 10000;
    // 传输超时时间，默认30秒
    private  int connectTimeout = 30000;
    // 请求器的配置
    private  static RequestConfig requestConfig;
    // HTTP请求器
    private  static CloseableHttpClient httpClient;

    public Object list(String nickname, String consignee, String orderSn, LocalDateTime start, LocalDateTime end, List<Short> orderStatusArray,
                       Integer page, Integer limit, String sort, String order) {
        Map<String, Object> data = (Map)orderService.queryVoSelective(nickname, consignee, orderSn, start, end, orderStatusArray, page, limit, sort, order);
        return ResponseUtil.ok(data);
    }

    public Object detail(Integer id) {
        LitemallOrder order = orderService.findById(id);
        OrderInfoVo orderInfoVo = CloneUtil.clone(order, OrderInfoVo.class);
        if (!StringUtils.isEmpty(order.getInvoiceId())) {
            LitemallInvoice invoice = litemallInvoiceMapper.selectById(order.getInvoiceId());
            orderInfoVo.setOrderInvoiceVo(CloneUtil.clone(invoice, OrderInvoiceVo.class));
        }
        if (!StringUtils.isEmpty(order.getShipChannel())) {
            ExpressInfoVO expressInfo = expressService.getExpressInfo(order.getShipChannel(), order.getShipSn(), order.getMobile());
            List<TracesVO> collect = expressInfo.getTraces().stream().sorted(Comparator.comparing(TracesVO::getTime).reversed()).collect(Collectors.toList());
            expressInfo.setTraces(collect);
            orderInfoVo.setExpressInfoVO(expressInfo);
        }
        List<LitemallOrderGoods> orderGoods = orderGoodsService.queryByOid(id);
        UserVo user = userService.findUserVoById(order.getUserId());
        Map<String, Object> data = new HashMap<>();
        data.put("order", orderInfoVo);
        data.put("orderGoods", orderGoods);
        data.put("user", user);

        return ResponseUtil.ok(data);
    }

    public Object refund(Integer orderId, String reason) {
        LitemallOrder order = new LitemallOrder();
        order.setId(orderId);
        order.setOrderStatus(OrderUtil.STATUS_WAIT_ACCEPT);
        order.setRefundContent(reason);
        return ResponseUtil.ok(orderService.updateByOrderId(order) > 0);
    }

    /**
     * 订单退款
     * <p>
     * 1. 检测当前订单是否能够退款;
     * 2. 微信退款操作;
     * 3. 设置订单退款确认状态；
     * 4. 订单商品库存回库。
     * <p>
     * TODO
     * 虽然接入了微信退款API，但是从安全角度考虑，建议开发者删除这里微信退款代码，采用以下两步走步骤：
     * 1. 管理员登录微信官方支付平台点击退款操作进行退款
     * 2. 管理员登录litemall管理后台点击退款操作进行订单状态修改和商品库存回库
     *
     * @param orderId 订单信息，{ orderId：xxx }
     * @return 订单退款操作结果
     */
    @Transactional
    public Object agree(Integer orderId) throws Exception {
        if (orderId == null) {
            return ResponseUtil.badArgument();
        }

        LitemallOrder order = orderService.findById(orderId);
        if (order == null) {
            return ResponseUtil.badArgument();
        }

        // 如果订单不是退款状态，则不能退款
        if (!order.getOrderStatus().equals(OrderUtil.STATUS_APPLY_REFUND)) {
            return ResponseUtil.fail(ORDER_CONFIRM_NOT_ALLOWED, "订单不能进行退款");
        }
        // 状态码
        String code = Constants.CODE_SUCCESS;
        //提示信息
        String msg = Constants.REFUND_SUCCESS;
        Map <String,String> data = new HashMap<String,String>();

        try {
            // 退款到用户微信
            String nonce_str = getRandomStringByLength(32);
            data.put("appid", Constants.appID);
            data.put("mch_id", Constants.MCH_ID);
            data.put("nonce_str", nonce_str);
            data.put("sign_type", "MD5");
            //商户订单号
            data.put("out_trade_no", order.getOrderSn());
            //商户退款单号
            data.put("out_refund_no", UuidUtil.generateUuid());
            // 把原来单位是元的数值保存，要存入数据库
            BigDecimal total_fee = order.getActualPrice().multiply(new BigDecimal("100"));
            // 微信那边是分所以乘100
            String fee = total_fee.toString();
            //支付金额，微信支付提交的金额是不能带小数点的，且是以分为单位,这边需要转成字符串类型，否则后面的签名会失败
            data.put("total_fee", fee.split("\\.")[0]);
            //退款总金额,订单总金额,单位为分,只能为整数
            data.put("refund_fee", fee.split("\\.")[0]);
            // 把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
            String preStr = PayUtil.createLinkString(data);
            //MD5运算生成签名，这里是第一次签名，用于调用统一下单接口
            String mySign = PayUtil.sign(preStr, Constants.key, "utf-8").toUpperCase();
            data.put("sign", mySign);

            //拼接统一下单接口使用的xml数据，要将上一步生成的签名一起拼接进去
            //支付结果通知的xml格式数据
            String xmlStr = postData(Constants.REFUND_PATH, PayUtil.GetMapToXML(data));
            System.out.println(xmlStr);
            Map notifyMap = PayUtil.doXMLParse(xmlStr);
            if ("SUCCESS".equals(notifyMap.get("result_code"))) {
                //退款成功的操作
                //返回的预付单信息
                String prepay_id = (String) notifyMap.get("prepay_id");
                System.out.println(prepay_id);
                Long timeStamp = System.currentTimeMillis() / 1000;
                //拼接签名需要的参数
                String stringSignTemp = "appId=" + Constants.appID + "&nonceStr=" + nonce_str + "&package=prepay_id=" + prepay_id + "&signType=MD5&timeStamp=" + timeStamp;
                //签名算法生成签名
                String paySign = PayUtil.sign(stringSignTemp, Constants.key, "utf-8").toUpperCase();
                data.put("package", "prepay_id=" + prepay_id);
                data.put("timeStamp", String.valueOf(timeStamp));
                data.put("paySign", paySign);

                // 退款之后的业务操作
                LocalDateTime now = LocalDateTime.now();
                // 设置订单取消状态
                order.setOrderStatus(OrderUtil.STATUS_REFUND_SUCCESS);
                order.setEndTime(now);
                // 记录订单退款相关信息
                order.setRefundAmount(order.getActualPrice());
                order.setRefundType("微信退款接口");
                order.setRefundTime(now);
                if (orderService.updateWithOptimisticLocker(order) == 0) {
                    throw new RuntimeException("更新数据已失效");
                }
                // 商品货品数量增加
                List<LitemallOrderGoods> orderGoodsList = orderGoodsService.queryByOid(orderId);
                // 获取订单是否在秒杀期间生成，且秒杀是否结束
                String addTime = DateUtil.getDateTimeDisplayString(order.getAddTime());
                for (LitemallOrderGoods orderGoods : orderGoodsList) {
                    // 秒杀是否结束
                    LitemallGoods killTime = goodsService.findByGoodIdBetween(orderGoods.getGoodsId(), DateUtil.currentTime());
                    if (null != killTime) {
                        // 下单时间是否在秒杀期间
                        LitemallGoods orderTime = goodsService.findByGoodIdBetween(orderGoods.getGoodsId(), addTime);
                        if (null != orderTime) {
                            // 说明秒杀未结束，且商品下单是在秒杀期间，秒杀库存加1
                            goodsService.addKillStock(orderGoods.getGoodsId(), Short.valueOf("1"));
                        }
                    }
                    Short number = orderGoods.getNumber();
                    if (goodsService.addStock(orderGoods.getGoodsId(), number) == 0) {
                        throw new RuntimeException("商品货品库存增加失败");
                    }
                }
            }else{
                System.out.println("退款失败:原因"+notifyMap.get("return_msg"));
                code = Constants.CODE_ERROR;
                msg = (String)notifyMap.get("return_msg");
            }
        }catch (Exception e) {
            logger.error("退款失败", e);
        }
        Map <String,Object> jsonResult = new HashMap<>();
        jsonResult.put("code",code);
        jsonResult.put("msg",msg);
        jsonResult.put("data",data);
        logger.info(jsonResult);

        //TODO 发送邮件和短信通知，这里采用异步发送
        // 退款成功通知用户, 例如“您申请的订单退款 [ 单号:{1} ] 已成功，请耐心等待到账。”
        // 注意订单号只发后6位
//        notifyService.notifySmsTemplate(order.getMobile(), NotifyType.REFUND,
//                new String[]{order.getOrderSn().substring(8, 14)});

        logHelper.logOrderSucceed("退款", "订单编号 " + order.getOrderSn());
        return ResponseUtil.ok();
    }


    /**
     * 加载证书
     *
     */
    private void initCert() throws Exception {
        // 证书密码，默认为商户ID
        String key = Constants.MCH_ID;
        // 商户证书的路径
//        String path = Constants.CERT_PATH;

        // 指定读取证书格式为PKCS12
        KeyStore keyStore = KeyStore.getInstance("PKCS12");

        // 读取本机存放的PKCS12证书文件
        File file = null;
        file = ResourceUtils.getFile("/home/resource/apiclient_cert.p12");
        // 获取文件输入流
        InputStream inputStream = new FileInputStream(file);
//        InputStream instream = new FileInputStream((ResourceUtils.getFile("classpath:cert/apiclient_cert.p12")));
        try {
            // 指定PKCS12的密码(商户ID)
            keyStore.load(inputStream, key.toCharArray());
        } finally {
            inputStream.close();
        }

        SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, key.toCharArray()).build();

        // 指定TLS版本
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null,SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
        // 设置httpclient的SSLSocketFactory
        httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
    }


    /**
     * 通过Https往API post xml数据
     * @param url  API地址
     * @param xmlObj   要提交的XML数据对象
     * @return
     */
    public  String postData(String url, String xmlObj) {
        // 加载证书
        try {
            initCert();
        } catch (Exception e) {
            e.printStackTrace();
        }
        String result = null;
        HttpPost httpPost = new HttpPost(url);
        // 得指明使用UTF-8编码，否则到API服务器XML的中文不能被成功识别
        StringEntity postEntity = new StringEntity(xmlObj, "UTF-8");
        httpPost.addHeader("Content-Type", "text/xml");
        httpPost.setEntity(postEntity);
        // 根据默认超时限制初始化requestConfig
        requestConfig = RequestConfig.custom()
                .setSocketTimeout(socketTimeout)
                .setConnectTimeout(connectTimeout)
                .build();
        // 设置请求器的配置
        httpPost.setConfig(requestConfig);
        try {
            HttpResponse response = null;
            try {
                response = httpClient.execute(httpPost);
            }  catch (IOException e) {
                e.printStackTrace();
            }
            HttpEntity entity = response.getEntity();
            try {
                result = EntityUtils.toString(entity, "UTF-8");
            }  catch (IOException e) {
                e.printStackTrace();
            }
        } finally {
            httpPost.abort();
        }
        return result;
    }

    private  String getRandomStringByLength(int length) {
        String base = "abcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }

    /**
     * 发货
     * 1. 检测当前订单是否能够发货
     * 2. 设置订单发货状态
     *
     * @param shipDTO 订单信息，{ orderId：xxx, shipSn: xxx, shipChannel: xxx }
     * @return 订单操作结果
     * 成功则 { errno: 0, errmsg: '成功' }
     * 失败则 { errno: XXX, errmsg: XXX }
     */
    public Object ship(ShipDTO shipDTO) {
        Integer orderId = shipDTO.getOrderId();
        String shipSn = shipDTO.getShipSn();
        String shipChannel = shipDTO.getShipChannel();
        Integer shipType = shipDTO.getShipType();

        LitemallOrder order = orderService.findById(orderId);
        if (order == null) {
            return ResponseUtil.badArgument();
        }

        // 如果订单不是已付款状态，则不能发货
        if (!order.getOrderStatus().equals(OrderUtil.STATUS_WAIT_SEND)) {
            return ResponseUtil.fail(ORDER_CONFIRM_NOT_ALLOWED, "订单不能确认收货");
        }

        order.setOrderStatus(OrderUtil.STATUS_WAIT_ACCEPT);
        order.setShipType(shipType);
        if (0 == shipType) {
            order.setShipSn(shipSn);
            order.setShipChannel(shipChannel);
        }
        order.setShipTime(LocalDateTime.now());

        if (orderService.updateWithOptimisticLocker(order) == 0) {
            return ResponseUtil.updatedDateExpired();
        }

        //TODO 发送邮件和短信通知，这里采用异步发送
        // 发货会发送通知短信给用户:          *
        // "您的订单已经发货，快递公司 {1}，快递单 {2} ，请注意查收"
        notifyService.notifySmsTemplate(order.getMobile(), NotifyType.SHIP, new String[]{shipChannel, shipSn});

        logHelper.logOrderSucceed("发货", "订单编号 " + order.getOrderSn());
        return ResponseUtil.ok();
    }

    /**
     * 删除订单
     * 1. 检测当前订单是否能够删除
     * 2. 删除订单
     *
     * @param orderId 订单信息，{ orderId：xxx }
     * @return 订单操作结果
     * 成功则 { errno: 0, errmsg: '成功' }
     * 失败则 { errno: XXX, errmsg: XXX }
     */
    public Object delete(Integer orderId) {
        LitemallOrder order = orderService.findById(orderId);
        if (order == null) {
            return ResponseUtil.badArgument();
        }

        // 如果订单不是关闭状态(已取消、系统取消、已退款、用户已确认、系统已确认)，则不能删除
        Short status = order.getOrderStatus();
        if (!status.equals(OrderUtil.STATUS_REFUND_FAIL) && !status.equals(OrderUtil.STATUS_CANCEL) &&
                !status.equals(OrderUtil.STATUS_COMPLETE)) {
            return ResponseUtil.fail(ORDER_DELETE_FAILED, "订单不能删除");
        }
        // 删除订单
        orderService.deleteById(orderId);
        // 删除订单商品
        orderGoodsService.deleteByOrderId(orderId);
        logHelper.logOrderSucceed("删除", "订单编号 " + order.getOrderSn());
        return ResponseUtil.ok();
    }

    /**
     * 回复订单商品
     *
     * @param body 订单信息，{ orderId：xxx }
     * @return 订单操作结果
     * 成功则 { errno: 0, errmsg: '成功' }
     * 失败则 { errno: XXX, errmsg: XXX }
     */
    public Object reply(String body) {
        Integer commentId = JacksonUtil.parseInteger(body, "commentId");
        if (commentId == null || commentId == 0) {
            return ResponseUtil.badArgument();
        }
        // 目前只支持回复一次
        LitemallComment comment = commentService.findById(commentId);
        if(comment == null){
            return ResponseUtil.badArgument();
        }
        if (!StringUtils.isEmpty(comment.getAdminContent())) {
            return ResponseUtil.fail(ORDER_REPLY_EXIST, "订单商品已回复！");
        }
        String content = JacksonUtil.parseString(body, "content");
        if (StringUtils.isEmpty(content)) {
            return ResponseUtil.badArgument();
        }
        // 更新评价回复
        comment.setAdminContent(content);
        commentService.updateById(comment);

        return ResponseUtil.ok();
    }

    public Object pay(String body) {
        Integer orderId = JacksonUtil.parseInteger(body, "orderId");
        String newMoney = JacksonUtil.parseString(body, "newMoney");

        if (orderId == null || StringUtils.isEmpty(newMoney)) {
            return ResponseUtil.badArgument();
        }
        BigDecimal actualPrice = new BigDecimal(newMoney);

        LitemallOrder order = orderService.findById(orderId);
        if (order == null) {
            return ResponseUtil.badArgument();
        }
        if (!order.getOrderStatus().equals(OrderUtil.STATUS_CREATE)) {
            return ResponseUtil.fail(ORDER_PAY_FAILED, "当前订单状态不支持线下收款");
        }

        order.setActualPrice(actualPrice);
        order.setOrderStatus(OrderUtil.STATUS_WAIT_SEND);
        if (orderService.updateWithOptimisticLocker(order) == 0) {
            return WxPayNotifyResponse.fail("更新数据已失效");
        }

        return ResponseUtil.ok();
    }
}
